package org.example.spring.factory;

import com.sun.xml.internal.ws.util.StringUtils;
import org.example.spring.annotation.MyAutowired;
import org.example.spring.config.MyGenericBeanDefinition;
import org.example.spring.config.MyPropertyValue;
import org.example.spring.exception.MyBeansException;
import org.example.spring.util.NameGenerator;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class MyDefaultListableBeanFactory implements MyBeanFactory {

    public static final String SET_PREFIX = "set";

    private final Map<String, MyGenericBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    private final Map<String, Object> singletonObjectMap = new ConcurrentHashMap<>(256);

    @Override
    public void registerBeanDefinition(String beanName, MyGenericBeanDefinition myGenericBeanDefinition) {
        beanDefinitionMap.put(beanName, myGenericBeanDefinition);
    }

    @Override
    public void preInstantiateSingletons() {
        this.preInstantiateSingletons(null);
    }

    public void preInstantiateSingletons(String refBeanNames) {
        Set<String> beanNameSet = beanDefinitionMap.keySet();

        if (StringUtils.isNotEmpty(refBeanNames)) {
            beanNameSet = new HashSet<>();
            beanNameSet.add(refBeanNames);
        }

        try {
            for (String beanName : beanNameSet) {

                if (beanName.endsWith(".full")) {


                }

                Object singletonObject = singletonObjectMap.get(beanName);
                if (singletonObject != null) {
                    continue;
                }

                MyGenericBeanDefinition myGenericBeanDefinition = beanDefinitionMap.get(beanName);

                if (myGenericBeanDefinition == null) {
                    throw new MyBeansException("BeanDefinition not null...");
                }

                Class beanClass = myGenericBeanDefinition.getBeanClass();
                Object beanObject = beanClass.getConstructor().newInstance();

                List<MyPropertyValue> myPropertyValueList = myGenericBeanDefinition.getMyPropertyValueList();
                for (MyPropertyValue item : myPropertyValueList) {
                    String name = item.getName();
                    String value = item.getValue();
                    String ref = item.getRef();

                    if (StringUtils.isEmpty(value) && StringUtils.isEmpty(ref)) {
                        throw new MyBeansException("property config od value and ref is null");
                    } else if (StringUtils.isNotEmpty(value) && StringUtils.isNotEmpty(ref)) {
                        throw new MyBeansException("property config and ref only configured with ");
                    }

                    Class parameterClass = beanClass.getDeclaredField(name).getType();
                    Method writeMethod = beanClass.getDeclaredMethod(SET_PREFIX + NameGenerator.beanUpperCase());
                    writeMethod.setAccessible(true);

                    if (StringUtils.isNotEmpty(value)) {
                        writeMethod.invoke(beanObject, TypeCastUtils.castType(value, parameterClass));
                    } else if (StringUtils.isNotEmpty(ref)) {
                        Object object = singletonObjectMap.get(ref);

                        if (object == null) {
                            preInstantiateSingletons(ref);
                        }

                        Object refBeanObject = singletonObjectMap.get(ref);
                        writeMethod.invoke(beanObject, refBeanObject);
                    }
                }

                Field[] declaredFields = beanClass.getDeclaredFields();
                for (Field field : declaredFields) {
                    if (!field.isAnnotationPresent(MyAutowired.class)) {
                        continue;
                    }

                    MyAutowired myAutowired = field.getDeclaredAnnotation(MyAutowired.class);
                    String autowiredBeanName = myAutowired.value().trim();

                    if ("".equals(autowiredBeanName)) {
                        autowiredBeanName = field.getType().getSimpleName();
                        autowiredBeanName = NameGenerator.nameGenerator(autowiredBeanName);
                    }

                    Object object = singletonObjectMap.get(autowiredBeanName);
                    if (object == null) {
                        preInstantiateSingletons(autowiredBeanName);

                        Object initObject = singletonObjectMap.get(autowiredBeanName);
                        field.setAccessible(true);
                        field.set(beanObject, initObject);
                    } else {
                        field.setAccessible(true);
                        field.set(beanObject, object);
                    }
                }

                singletonObjectMap.put(beanName, beanObject);
            }
        } catch (RuntimeException e) {
            throw new RuntimeException(e);
        } catch (MyBeansException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }


    }

    @Override
    public <T> T getBean(String beanName, Class<T> beanClassType) {
        Object beanObject = singletonObjectMap.get(beanName);

        return (T) beanObject;
    }
}
